/* NetHack 3.6  makedefs.c  $NHDT-Date: 1562180226 2019/07/03 18:57:06 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.149 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Kenneth Lorber, Kensington, Maryland, 2015. */
/* Copyright (c) M. Stephenson, 1990, 1991.                       */
/* Copyright (c) Dean Luick, 1990.                                */
/* NetHack may be freely redistributed.  See license for details. */

#define MAKEDEFS_C /* use to conditionally include file sections */

#include "config.h"
#ifdef MONITOR_HEAP
#undef free /* makedefs doesn't use the alloc and free in src/alloc.c */
#endif
#include "permonst.h"
#include "objclass.h"
#include "monsym.h"
#include "artilist.h"
#include "dungeon.h"
#include "obj.h"
#include "monst.h"
#include "you.h"
#include "context.h"
#include "flag.h"
#include "dlb.h"

/* version information */
#ifdef SHORT_FILENAMES
#include "patchlev.h"
#else
#include "patchlevel.h"
#endif

#include <ctype.h>
#ifdef MAC
#if defined(__SC__) || defined(__MRC__) /* MPW compilers */
#define MPWTOOL
#include <CursorCtl.h>
#include <string.h>
#else /* MAC without MPWTOOL */
#define MACsansMPWTOOL
#endif
#endif /* MAC */

#ifndef MPWTOOL
#define SpinCursor(x)
#endif

#define Fprintf (void) fprintf
#define Fclose (void) fclose
#define Unlink (void) unlink
#if !defined(AMIGA) || defined(AZTEC_C)
#define rewind(fp) fseek((fp), 0L, SEEK_SET) /* guarantee a return value */
#endif

#if defined(UNIX) && !defined(LINT) && !defined(GCC_WARN)
static const char SCCS_Id[] UNUSED = "@(#)makedefs.c\t3.6\t2019/05/07";
#endif

/* names of files to be generated */
#define DATE_FILE "date.h"
#define MONST_FILE "pm.h"
#define ONAME_FILE "onames.h"
#ifndef OPTIONS_FILE
#define OPTIONS_FILE "options"
#endif
#define ORACLE_FILE "oracles"
#define DATA_FILE "data"
#define RUMOR_FILE "rumors"
#define DGN_I_FILE "dungeon.def"
#define DGN_O_FILE "dungeon.pdf"
#define MON_STR_C "monstr.c"
#define QTXT_I_FILE "quest.txt"
#define QTXT_O_FILE "quest.dat"
#define VIS_TAB_H "vis_tab.h"
#define VIS_TAB_C "vis_tab.c"
#define GITINFO_FILE "gitinfo.txt"
/* locations for those files */
#ifdef AMIGA
#define FILE_PREFIX
#define INCLUDE_TEMPLATE "NH:include/t.%s"
#define SOURCE_TEMPLATE "NH:src/%s"
#define DGN_TEMPLATE "NH:dat/%s" /* where dungeon.pdf file goes */
#define DATA_TEMPLATE "NH:slib/%s"
#define DATA_IN_TEMPLATE "NH:dat/%s"
#else /* not AMIGA */
#if defined(MAC) && !defined(__MACH__)
/* MacOS 9 or earlier */
#define INCLUDE_TEMPLATE ":include:%s"
#define SOURCE_TEMPLATE ":src:%s"
#define DGN_TEMPLATE ":dat:%s" /* where dungeon.pdf file goes */
#if __SC__ || __MRC__
#define DATA_TEMPLATE ":Dungeon:%s"
#else
#define DATA_TEMPLATE ":lib:%s"
#endif /* __SC__ || __MRC__ */
#define DATA_IN_TEMPLATE ":dat:%s"
#else /* neither AMIGA nor MAC */
#ifdef OS2
#define INCLUDE_TEMPLATE "..\\include\\%s"
#define SOURCE_TEMPLATE "..\\src\\%s"
#define DGN_TEMPLATE "..\\dat\\%s" /* where dungeon.pdf file goes */
#define DATA_TEMPLATE "..\\dat\\%s"
#define DATA_IN_TEMPLATE "..\\dat\\%s"
#else /* not AMIGA, MAC, or OS2 */
#define INCLUDE_TEMPLATE "../include/%s"
#define SOURCE_TEMPLATE "../src/%s"
#define DGN_TEMPLATE "../dat/%s" /* where dungeon.pdf file goes */
#define DATA_TEMPLATE "../dat/%s"
#define DATA_IN_TEMPLATE "../dat/%s"
#endif /* else !OS2 */
#endif /* else !MAC */
#endif /* else !AMIGA */

static const char
    *Dont_Edit_Code =
        "/* This source file is generated by 'makedefs'.  Do not edit. */\n",
    *Dont_Edit_Data =
        "#\tThis data file is generated by 'makedefs'.  Do not edit. \n";

static struct version_info version;

/* definitions used for vision tables */
#define TEST_WIDTH COLNO
#define TEST_HEIGHT ROWNO
#define BLOCK_WIDTH (TEST_WIDTH + 10)
#define BLOCK_HEIGHT TEST_HEIGHT /* don't need extra spaces */
#define MAX_ROW (BLOCK_HEIGHT + TEST_HEIGHT)
#define MAX_COL (BLOCK_WIDTH + TEST_WIDTH)
/* Use this as an out-of-bound value in the close table.  */
#define CLOSE_OFF_TABLE_STRING "99" /* for the close table */
#define FAR_OFF_TABLE_STRING "0xff" /* for the far table */

#define sign(z) ((z) < 0 ? -1 : ((z) ? 1 : 0))
#ifdef VISION_TABLES
static char xclear[MAX_ROW][MAX_COL];
#endif
/*-end of vision defs-*/

#define MAXFNAMELEN 600

static char filename[MAXFNAMELEN];

#ifdef FILE_PREFIX
/* if defined, a first argument not starting with - is
 * taken as a text string to be prepended to any
 * output filename generated */
char *file_prefix = "";
#endif

#ifdef MACsansMPWTOOL
int FDECL(main, (void));
#else
int FDECL(main, (int, char **));
#endif
void FDECL(do_makedefs, (char *));
void NDECL(do_objs);
void NDECL(do_data);
void NDECL(do_dungeon);
void NDECL(do_date);
void NDECL(do_options);
void NDECL(do_monstr);
void NDECL(do_permonst);
void NDECL(do_questtxt);
void NDECL(do_rumors);
void NDECL(do_oracles);
void NDECL(do_vision);

extern void NDECL(monst_init);   /* monst.c */
extern void NDECL(objects_init); /* objects.c */

static void NDECL(link_sanity_check);
static char *FDECL(name_file, (const char *, const char *));
static void FDECL(delete_file, (const char *template, const char *));
static FILE *FDECL(getfp, (const char *, const char *, const char *));
static void FDECL(do_ext_makedefs, (int, char **));

static void NDECL(make_version);
static char *FDECL(version_string, (char *, const char *));
static char *FDECL(version_id_string, (char *, const char *));
static char *FDECL(bannerc_string, (char *, const char *));
static char *FDECL(xcrypt, (const char *));
static unsigned long FDECL(read_rumors_file,
                           (const char *, int *, long *, unsigned long));
static boolean FDECL(get_gitinfo, (char *, char *));
static void FDECL(do_rnd_access_file, (const char *));
static boolean FDECL(d_filter, (char *));
static boolean FDECL(h_filter, (char *));
static void NDECL(build_savebones_compat_string);
static void NDECL(windowing_sanity);
static void FDECL(opt_out_words, (char *, int *));

static boolean FDECL(qt_comment, (char *));
static boolean FDECL(qt_control, (char *));
static int FDECL(get_hdr, (char *));
static boolean FDECL(new_id, (char *));
static boolean FDECL(known_msg, (int, int));
static void FDECL(new_msg, (char *, int, int));
static char *FDECL(valid_qt_summary, (char *, BOOLEAN_P));
static void FDECL(do_qt_control, (char *));
static void FDECL(do_qt_text, (char *));
static void NDECL(adjust_qt_hdrs);
static void NDECL(put_qt_hdrs);

#ifdef VISION_TABLES
static void NDECL(H_close_gen);
static void NDECL(H_far_gen);
static void NDECL(C_close_gen);
static void NDECL(C_far_gen);
static int FDECL(clear_path, (int, int, int, int));
#endif

static char *FDECL(fgetline, (FILE*));
static char *FDECL(tmpdup, (const char *));
static char *FDECL(limit, (char *, int));
static char *FDECL(eos, (char *));
static int FDECL(case_insensitive_comp, (const char *, const char *));

/* input, output, tmp */
static FILE *ifp, *ofp, *tfp;

static boolean use_enum =
#ifdef ENUM_PM
    TRUE;
#else
    FALSE;
#endif

#if defined(__BORLANDC__) && !defined(_WIN32)
extern unsigned _stklen = STKSIZ;
#endif

#ifdef MACsansMPWTOOL
int
main(void)
{
    const char *def_options = "odemvpqrshz";
    char buf[100];
    int len;

    printf("Enter options to run: [%s] ", def_options);
    fflush(stdout);
    fgets(buf, 100, stdin);
    len = strlen(buf);
    if (len <= 1)
        Strcpy(buf, def_options);
    else
        buf[len - 1] = 0; /* remove return */

    if (buf[0] == '-' && buf[1] == '-') {
#if 0
        split up buf into words
        do_ext_makedefs(fakeargc, fakeargv);
#else
        printf("extended makedefs not implemented for Mac OS9\n");
        exit(EXIT_FAILURE);
#endif
    }

    do_makedefs(buf);
    exit(EXIT_SUCCESS);
    return 0;
}

#else /* ! MAC */

int
main(argc, argv)
int argc;
char *argv[];
{
    if ((argc == 1) ||
        ((argc != 2)
#ifdef FILE_PREFIX
        && (argc != 3)
#endif
        && !(argv[1][0] == '-' && argv[1][1] == '-'))) {
        Fprintf(stderr, "Bad arg count (%d).\n", argc - 1);
        (void) fflush(stderr);
        return 1;
    }

#ifdef FILE_PREFIX
    if (argc >= 2 && argv[1][0] != '-') {
        file_prefix = argv[1];
        argc--;
        argv++;
    }
#endif

    if (argv[1][0] == '-' && argv[1][1] == '-') {
        do_ext_makedefs(argc, argv);
    } else {
        do_makedefs(&argv[1][1]);
    }
    exit(EXIT_SUCCESS);
    /*NOTREACHED*/
    return 0;
}

#endif

static void
link_sanity_check()
{
    /* Note:  these initializers don't do anything except guarantee that
            we're linked properly.
    */
    monst_init();
    objects_init();

}

void
do_makedefs(options)
char *options;
{
    boolean more_than_one;

    link_sanity_check();

    /* construct the current version number */
    make_version();

    more_than_one = strlen(options) > 1;
    while (*options) {
        if (more_than_one)
            Fprintf(stderr, "makedefs -%c\n", *options);

        switch (*options) {
        case 'o':
        case 'O':
            do_objs();
            break;
        case 'd':
        case 'D':
            do_data();
            break;
        case 'e':
        case 'E':
            do_dungeon();
            break;
        case 'm':
        case 'M':
            do_monstr();
            break;
        case 'v':
        case 'V':
            do_date();
            do_options();
            break;
        case 'p':
        case 'P':
            do_permonst();
            break;
        case 'q':
        case 'Q':
            do_questtxt();
            break;
        case 'r':
        case 'R':
            do_rumors();
            break;
        case 's':
        case 'S':
            do_rnd_access_file(EPITAPHFILE);
            do_rnd_access_file(ENGRAVEFILE);
            do_rnd_access_file(BOGUSMONFILE);
            break;
        case 'h':
        case 'H':
            do_oracles();
            break;
        case 'z':
        case 'Z':
            do_vision();
            break;

        default:
            Fprintf(stderr, "Unknown option '%c'.\n", *options);
            (void) fflush(stderr);
            exit(EXIT_FAILURE);
        }
        options++;
    }
    if (more_than_one)
        Fprintf(stderr, "Completed.\n"); /* feedback */
}

static char namebuf[1000];

static char *
name_file(template, tag)
const char *template;
const char *tag;
{
    Sprintf(namebuf, template, tag);
    return namebuf;
}

static void
delete_file(template, tag)
const char *template;
const char *tag;
{
    char *name = name_file(template, tag);

    Unlink(name);
}

static FILE *
getfp(template, tag, mode)
const char *template;
const char *tag;
const char *mode;
{
    char *name = name_file(template, tag);
    FILE *rv = fopen(name, mode);

    if (!rv) {
        Fprintf(stderr, "Can't open '%s'.\n", name);
        exit(EXIT_FAILURE);
    }
    return rv;
}

static boolean debug = FALSE;

static FILE *inputfp;
static FILE *outputfp;

struct grep_var {
    const char *name;
    int is_defined; /* 0 undef; 1 defined */
};
/* struct grep_var grep_vars[] and TODO_* constants in include file: */
#include "mdgrep.h"

static void NDECL(do_grep_showvars);
static struct grep_var *FDECL(grepsearch, (const char *));
static int FDECL(grep_check_id, (const char *));
static void FDECL(grep_show_wstack, (const char *));
static char *FDECL(do_grep_control, (char *));
static void NDECL(do_grep);
static void FDECL(grep0, (FILE *, FILE *));

static int grep_trace = 0;

#define IS_OPTION(str) if (!strcmp(&argv[0][2], str))
#define CONTINUE    \
    argv++, argc--; \
    continue
#define CONSUME                              \
    argv++, argc--;                          \
    if (argc == 0) {                         \
        Fprintf(stderr, "missing option\n"); \
        exit(EXIT_FAILURE);                  \
    }

static void
do_ext_makedefs(int argc, char **argv)
{
    int todo = 0;

    link_sanity_check();

    argc--;
    argv++; /* skip program name */

    while (argc) {
        if (argv[0][0] != '-')
            break;
        if (argv[0][1] != '-') {
            Fprintf(stderr, "Can't mix - and -- options.\n");
            exit(EXIT_FAILURE);
        }
        IS_OPTION("svs") {
            /* short version string for packaging - note no \n */
            char buf[100];
            char delim[10];

            argv++; /* not CONSUME */
            delim[0] = '\0';
            if (argv[0])
                strcpy(delim, argv[0]);
            Fprintf(stdout, "%s", version_string(buf, delim));
            exit(EXIT_SUCCESS);
        }
        IS_OPTION("debug") {
            debug = TRUE;
            CONTINUE;
        }
        IS_OPTION("make") {
            CONSUME;
            do_makedefs(argv[0]);
            exit(EXIT_SUCCESS);
        }
        IS_OPTION("input") {
            CONSUME;
            if (!strcmp(argv[0], "-")) {
                inputfp = stdin;
            } else {
                inputfp = fopen(argv[0], RDTMODE);
                if (!inputfp) {
                    Fprintf(stderr, "Can't open '%s'.\n", argv[0]);
                    exit(EXIT_FAILURE);
                }
            }
            CONTINUE;
        }
        IS_OPTION("output") {
            CONSUME;
            if (!strcmp(argv[0], "-")) {
                outputfp = stdout;
            } else {
                outputfp = fopen(argv[0], WRTMODE);
                if (!outputfp) {
                    Fprintf(stderr, "Can't open '%s'.\n", argv[0]);
                    exit(EXIT_FAILURE);
                }
            }
            CONTINUE;
        }
        IS_OPTION("grep") {
            if (todo) {
                Fprintf(stderr, "Can't do grep and something else.\n");
                exit(EXIT_FAILURE);
            }
            todo = TODO_GREP;
            CONTINUE;
        }
        IS_OPTION("grep-showvars") {
            do_grep_showvars();
            exit(EXIT_SUCCESS);
        }
        IS_OPTION("grep-trace") {
            grep_trace = 1;
            CONTINUE;
        }
        IS_OPTION("grep-define") {
            struct grep_var *p;

            CONSUME;
            p = grepsearch(argv[0]);
            if (p) {
                p->is_defined = 1;
            } else {
                Fprintf(stderr, "Unknown symbol '%s'\n", argv[0]);
                exit(EXIT_FAILURE);
            }
            CONTINUE;
        }
        IS_OPTION("grep-undef") {
            struct grep_var *p;

            CONSUME;
            p = grepsearch(argv[0]);
            if (p) {
                p->is_defined = 0;
            } else {
                Fprintf(stderr, "Unknown symbol '%s'\n", argv[0]);
                exit(EXIT_FAILURE);
            }
            CONTINUE;
        }
#ifdef notyet
        IS_OPTION("help") {
        }
#endif
        Fprintf(stderr, "Unknown option '%s'.\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    if (argc) {
        Fprintf(stderr, "unexpected argument '%s'.\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    switch (todo) {
    default:
        Fprintf(stderr, "Confused about what to do?\n");
        exit(EXIT_FAILURE);
    case 0:
        Fprintf(stderr, "Nothing to do?\n");
        exit(EXIT_FAILURE);
    case TODO_GREP:
        do_grep();
        break;
    }
}

#undef IS_OPTION
#undef CONTINUE
#undef CONSUME

/*
 * Filtering syntax:
 * Any line NOT starting with a caret is either suppressed or passed
 * through unchanged depending on the current conditional state.
 *
 * The default conditional state is printing on.
 *
 * Conditionals may be nested.
 *
 * makedefs will exit with a EXIT_FAILURE if any errors are detected;
 * as many errors as possible are detected before giving up.
 *
 * Unknown identifiers are treated as TRUE and also as an error to
 * allow processing to continue past the unknown identifier (note
 * that "#undef" is different than unknown).
 *
 * Any line starting with a caret is a control line; as in C, zero or
 * more spaces may be embedded in the line almost anywhere; the caret
 * MUST be in column 1.
 * (XXX for the moment, no white space is allowed after the caret because
 * existing lines in the docs look like that.)
 *
 * Control lines:
 *      ^^      a line starting with a (single) literal caret
 *      ^#      a comment - the line is ignored
 *      ^?ID    if defined(ID)
 *      ^!ID    if !defined(ID)
 *      ^:      else
 *      ^.      endif
 */
#define GREP_MAGIC '^'
#define GREP_STACK_SIZE 100
#ifdef notyet
static int grep_rewrite = 0; /* need to (possibly) rewrite lines */
#endif
static int grep_writing = 1; /* need to copy lines to output */
static int grep_errors = 0;
static int grep_sp = 0;
#define ST_LD(old, opp) (((old) ? 1 : 0) | ((opp) ? 2 : 0))
#define ST_OLD(v) (((v) & 1) != 0)
#define ST_OPP(v) (((v) & 2) != 0)
#define ST_ELSE 4
static int grep_stack[GREP_STACK_SIZE] = { ST_LD(1, 0) };
static int grep_lineno = 0;

static void
do_grep_showvars()
{
    int x;

    for (x = 0; x < SIZE(grep_vars) - 1; x++) {
        printf("%d\t%s\n", grep_vars[x].is_defined, grep_vars[x].name);
    }
}

static struct grep_var *
grepsearch(name)
const char *name;
{
    /* XXX make into binary search */
    int x = 0;

    while (x < SIZE(grep_vars) - 1) {
        if (!strcmp(grep_vars[x].name, name))
            return &grep_vars[x];
        x++;
    }
    return 0;
}

static int
grep_check_id(id)
const char *id;
{
    struct grep_var *rv;

    while (*id && isspace((uchar) *id))
        id++;
    if (!*id) {
        Fprintf(stderr, "missing identifier in line %d", grep_lineno);
        grep_errors++;
        return 0;
    }
    rv = grepsearch(id);
    if (rv) {
        if (grep_trace) {
            Fprintf(outputfp, "ID %d %s\n", rv->is_defined, id);
        }
        return rv->is_defined;
    }

    if (grep_trace) {
        Fprintf(outputfp, "ID U %s\n", id);
    }
    Fprintf(stderr, "unknown identifier '%s' in line %d.\n", id, grep_lineno);
    grep_errors++;
    return 2; /* So new features can be checked before makedefs
               * is rebuilt. */
}

static void
grep_show_wstack(tag)
const char *tag;
{
    int x;

    if (!grep_trace)
        return;

    Fprintf(outputfp, "%s w=%d sp=%d\t", tag, grep_writing, grep_sp);
    for (x = grep_sp; x >= 0 && x > grep_sp - 6; x--) {
        Fprintf(outputfp, "[%d]=%d ", x, grep_stack[x]);
    }
    Fprintf(outputfp, "\n");
}

static char *
do_grep_control(buf)
char *buf;
{
    int isif = 1;
    char *buf0 = buf;
#if 1
    if (isspace((uchar) buf[0]))
        return &buf[-1]; /* XXX see docs above */
#else
    while (buf[0] && isspace((uchar) buf[0]))
        buf++;
#endif
    switch (buf[0]) {
    case '#': /* comment */
        break;
    case '.': /* end of if level */
        if (grep_sp == 0) {
            Fprintf(stderr, "unmatched ^. (endif) at line %d.\n",
                    grep_lineno);
            grep_errors++;
        } else {
            grep_writing = ST_OLD(grep_stack[grep_sp--]);
            grep_show_wstack("pop");
        }
        break;
    case '!': /* if not ID */
        isif = 0;
    /* FALLTHROUGH */
    case '?': /* if ID */
        if (grep_sp == GREP_STACK_SIZE - 2) {
            Fprintf(stderr, "stack overflow at line %d.", grep_lineno);
            exit(EXIT_FAILURE);
        }
        if (grep_writing) {
            isif = grep_check_id(&buf[1]) ? isif : !isif;
            grep_stack[++grep_sp] = ST_LD(grep_writing, !isif);
            grep_writing = isif;
        } else {
            grep_stack[++grep_sp] = ST_LD(0, 0);
            /* grep_writing = 0; */
        }
        grep_show_wstack("push");
        break;
    case ':': /* else */
        if (ST_ELSE & grep_stack[grep_sp]) {
            Fprintf(stderr, "multiple : for same conditional at line %d.\n",
                    grep_lineno);
            grep_errors++;
        }
        grep_writing = ST_OPP(grep_stack[grep_sp]);
        grep_stack[grep_sp] |= ST_ELSE;
        break;
#if defined(notyet)
    case '(': /* start of expression */
#endif
    case GREP_MAGIC: /* ^^ -> ^ */
        return buf0;
    default: {
        char str[10];

        if (isprint((uchar) buf[0])) {
            str[0] = buf[0];
            str[1] = '\0';
        } else {
            sprintf(str, "0x%02x", buf[0]);
        }
        Fprintf(stderr, "unknown control ^%s at line %d.\n", str,
                grep_lineno);
        grep_errors++;
    } break;
    }
    return NULL;
}

#ifdef notyet
static void
do_grep_rewrite(buf)
char *buf;
{
    /* no language features use this yet */
    return;
}
#endif

static void grep0(FILE *, FILE *);

static void
do_grep()
{
    if (!inputfp) {
        Fprintf(stderr, "--grep requires --input\n");
    }
    if (!outputfp) {
        Fprintf(stderr, "--grep requires --output\n");
    }
    if (!inputfp || !outputfp) {
        exit(EXIT_FAILURE);
    }

    grep0(inputfp, outputfp);
}

static void
grep0(inputfp0, outputfp0)
FILE *inputfp0;
FILE *outputfp0;
{
    char buf[16384]; /* looong, just in case */

    while (!feof(inputfp0) && !ferror(inputfp0)) {
        char *tmp;
        char *buf1;

        if (fgets(buf, sizeof(buf), inputfp0) == 0)
            break;
        if ((tmp = strchr(buf, '\n')))
            *tmp = '\0';
        grep_lineno++;
        if (grep_trace) {
            Fprintf(outputfp0, "%04d %c >%s\n", grep_lineno,
                    grep_writing ? ' ' : '#', buf);
        }

        if (buf[0] == GREP_MAGIC) {
            buf1 = do_grep_control(&buf[1]);
            if (!buf1)
                continue;
        } else {
            buf1 = buf;
        }
#ifdef notyet
        if (grep_rewrite)
            do_grep_rewrite(buf1);
#endif
        if (grep_writing)
            Fprintf(outputfp0, "%s\n", buf1);
    }
    if (ferror(inputfp0)) {
        Fprintf(stderr, "read error!\n");
        exit(EXIT_FAILURE);
    }
    if (ferror(outputfp0)) {
        Fprintf(stderr, "write error!\n");
        exit(EXIT_FAILURE);
    }
    fclose(inputfp0);
    fclose(outputfp0);
    if (grep_sp) {
        Fprintf(stderr, "%d unterminated conditional level%s\n", grep_sp,
                grep_sp == 1 ? "" : "s");
        grep_errors++;
    }
    if (grep_errors) {
        Fprintf(stderr, "%d error%s detected.\n", grep_errors,
                grep_errors == 1 ? "" : "s");
        exit(EXIT_FAILURE);
    }
}

/* trivial text encryption routine which can't be broken with `tr' */
static char *
xcrypt(str)
const char *str;
{ /* duplicated in src/hacklib.c */
    static char buf[BUFSZ];
    register const char *p;
    register char *q;
    register int bitmask;

    for (bitmask = 1, p = str, q = buf; *p; q++) {
        *q = *p++;
        if (*q & (32 | 64))
            *q ^= bitmask;
        if ((bitmask <<= 1) >= 32)
            bitmask = 1;
    }
    *q = '\0';
    return buf;
}

#define PAD_RUMORS_TO 60
/* common code for do_rumors().  Return 0 on error. */
static unsigned long
read_rumors_file(file_ext, rumor_count, rumor_size, old_rumor_offset)
const char *file_ext;
int *rumor_count;
long *rumor_size;
unsigned long old_rumor_offset;
{
    char infile[MAXFNAMELEN];
    char *line;
    unsigned long rumor_offset;

    Sprintf(infile, DATA_IN_TEMPLATE, RUMOR_FILE);
    Strcat(infile, file_ext);
    if (!(ifp = fopen(infile, RDTMODE))) {
        perror(infile);
        return 0L;
    }

    /* copy the rumors */
    while ((line = fgetline(ifp)) != 0) {
#ifdef PAD_RUMORS_TO
        /* rumor selection is accomplished by seeking to a random
           position in the file, advancing to newline, and taking
           the next line; therefore, rumors which follow long-line
           rumors are most likely to be chosen and rumors which
           follow short-line rumors are least likely to be chosen;
           we ameliorate the latter by padding the shortest lines,
           increasing the chance of the random seek landing in them */
        int len = (int) strlen(line);

        if (len <= PAD_RUMORS_TO) {
            char *base = index(line, '\n');
            /* this is only safe because fgetline() overallocates */
            while (len++ < PAD_RUMORS_TO) {
                *base++ = '_';
            }
            *base++ = '\n';
            *base = '\0';
        }
#endif
        (*rumor_count)++;
#if 0
        /*[if we forced binary output, this would be sufficient]*/
        *rumor_size += strlen(line); /* includes newline */
#endif
        (void) fputs(xcrypt(line), tfp);
        free(line);
    }
    /* record the current position; next rumors section will start here */
    rumor_offset = (unsigned long) ftell(tfp);
    Fclose(ifp); /* all done with rumors.file_ext */

    /* the calculated value for *_rumor_count assumes that
       a single-byte line terminator is in use; for platforms
       which use two byte CR+LF, we need to override that value
       [it's much simpler to do so unconditionally, rendering
       the loop's accumulation above obsolete] */
    *rumor_size = (long) (rumor_offset - old_rumor_offset);
    return rumor_offset;
}

static void
do_rnd_access_file(fname)
const char *fname;
{
    char *line;

    Sprintf(filename, DATA_IN_TEMPLATE, fname);
    Strcat(filename, ".txt");
    if (!(ifp = fopen(filename, RDTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), DATA_TEMPLATE, fname);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp, "%s", Dont_Edit_Data);

    tfp = getfp(DATA_TEMPLATE, "grep.tmp", WRTMODE);
    grep0(ifp, tfp);
    ifp = getfp(DATA_TEMPLATE, "grep.tmp", RDTMODE);

    while ((line = fgetline(ifp)) != 0) {
        if (line[0] != '#' && line[0] != '\n')
            (void) fputs(xcrypt(line), ofp);
        free(line);
    }
    Fclose(ifp);
    Fclose(ofp);

    delete_file(DATA_TEMPLATE, "grep.tmp");
    return;
}

void
do_rumors()
{
    char *line;
    static const char rumors_header[] =
        "%s%04d,%06ld,%06lx;%04d,%06ld,%06lx;0,0,%06lx\n";
    char tempfile[MAXFNAMELEN];
    int true_rumor_count, false_rumor_count;
    long true_rumor_size, false_rumor_size;
    unsigned long true_rumor_offset, false_rumor_offset, eof_offset;

    Sprintf(tempfile, DATA_TEMPLATE, "rumors.tmp");
    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), DATA_TEMPLATE, RUMOR_FILE);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    if (!(tfp = fopen(tempfile, WRTMODE))) {
        perror(tempfile);
        Fclose(ofp);
        exit(EXIT_FAILURE);
    }

    true_rumor_count = false_rumor_count = 0;
    true_rumor_size = false_rumor_size = 0L;
    true_rumor_offset = false_rumor_offset = eof_offset = 0L;

    /* output a dummy header record; we'll replace it in final output */
    Fprintf(tfp, rumors_header, Dont_Edit_Data, true_rumor_count,
            true_rumor_size, true_rumor_offset, false_rumor_count,
            false_rumor_size, false_rumor_offset, eof_offset);
    /* record the current position; true rumors will start here */
    true_rumor_offset = ftell(tfp);

    false_rumor_offset = read_rumors_file(
        ".tru", &true_rumor_count, &true_rumor_size, true_rumor_offset);
    if (!false_rumor_offset)
        goto rumors_failure;

    eof_offset = read_rumors_file(".fal", &false_rumor_count,
                                  &false_rumor_size, false_rumor_offset);
    if (!eof_offset)
        goto rumors_failure;

    /* get ready to transfer the contents of temp file to output file */
    line = malloc(BUFSZ + MAXFNAMELEN);
    Sprintf(line, "rewind of \"%s\"", tempfile);
    if (rewind(tfp) != 0) {
        perror(line);
        free(line);
        goto rumors_failure;
    }
    free(line);

    /* output the header record */
    Fprintf(ofp, rumors_header, Dont_Edit_Data, true_rumor_count,
            true_rumor_size, true_rumor_offset, false_rumor_count,
            false_rumor_size, false_rumor_offset, eof_offset);
    /* skip the temp file's dummy header */
    if (!(line = fgetline(tfp))) { /* "Don't Edit" */
        perror(tempfile);
        goto rumors_failure;
    }
    free(line);
    if (!(line = fgetline(tfp))) { /* count,size,offset */
        perror(tempfile);
        goto rumors_failure;
    }
    free(line);
    /* copy the rest of the temp file into the final output file */
    while ((line = fgetline(tfp)) != 0) {
        (void) fputs(line, ofp);
        free(line);
    }
    /* all done; delete temp file */
    Fclose(tfp);
    Unlink(tempfile);
    Fclose(ofp);
    return;

rumors_failure:
    Fclose(ofp);
    Unlink(filename); /* kill empty or incomplete output file */
    Fclose(tfp);
    Unlink(tempfile); /* and temporary file */
    exit(EXIT_FAILURE);
}

/*
 * Use this to explicitly mask out features during version checks.
 *
 * ZEROCOMP, RLECOMP, and ZLIB_COMP describe compression features
 * that the port/plaform which wrote the savefile was capable of
 * dealing with. Don't reject a savefile just because the port
 * reading the savefile doesn't match on all/some of them.
 * The actual compression features used to produce the savefile are
 * recorded in the savefile_info structure immediately following the
 * version_info, and that is what needs to be checked against the
 * feature set of the port that is reading the savefile back in.
 * That check is done in src/restore.c now.
 *
 */
#define IGNORED_FEATURES                 \
    (0L | (1L << 19) /* SCORE_ON_BOTL */ \
     | (1L << 27)    /* ZEROCOMP */      \
     | (1L << 28)    /* RLECOMP */       \
     )

static void
make_version()
{
    register int i;

    /*
     * integer version number
     */
    version.incarnation = ((unsigned long) VERSION_MAJOR << 24)
                          | ((unsigned long) VERSION_MINOR << 16)
                          | ((unsigned long) PATCHLEVEL << 8)
                          | ((unsigned long) EDITLEVEL);
    /*
     * encoded feature list
     * Note:  if any of these magic numbers are changed or reassigned,
     * EDITLEVEL in patchlevel.h should be incremented at the same time.
     * The actual values have no special meaning, and the category
     * groupings are just for convenience.
     */
    version.feature_set = (unsigned long) (0L
/* levels and/or topology (0..4) */
/* monsters (5..9) */
#ifdef MAIL
                                           | (1L << 6)
#endif
/* objects (10..14) */
/* flag bits and/or other global variables (15..26) */
#ifdef TEXTCOLOR
                                           | (1L << 17)
#endif
#ifdef INSURANCE
                                           | (1L << 18)
#endif
#ifdef SCORE_ON_BOTL
                                           | (1L << 19)
#endif
/* data format (27..31)
 * External compression methods such as COMPRESS and ZLIB_COMP
 * do not affect the contents and are thus excluded from here */
#ifdef ZEROCOMP
                                           | (1L << 27)
#endif
#ifdef RLECOMP
                                           | (1L << 28)
#endif
                                               );
    /*
     * Value used for object & monster sanity check.
     *    (NROFARTIFACTS<<24) | (NUM_OBJECTS<<12) | (NUMMONS<<0)
     */
    for (i = 1; artifact_names[i]; i++)
        continue;
    version.entity_count = (unsigned long) (i - 1);
    for (i = 1; objects[i].oc_class != ILLOBJ_CLASS; i++)
        continue;
    version.entity_count = (version.entity_count << 12) | (unsigned long) i;
    for (i = 0; mons[i].mlet; i++)
        continue;
    version.entity_count = (version.entity_count << 12) | (unsigned long) i;
    /*
     * Value used for compiler (word size/field alignment/padding) check.
     */
    version.struct_sizes1 =
        (((unsigned long) sizeof(struct context_info) << 24)
         | ((unsigned long) sizeof(struct obj) << 17)
         | ((unsigned long) sizeof(struct monst) << 10)
         | ((unsigned long) sizeof(struct you)));
    version.struct_sizes2 = (((unsigned long) sizeof(struct flag) << 10) |
/* free bits in here */
#ifdef SYSFLAGS
                             ((unsigned long) sizeof(struct sysflag)));
#else
                             ((unsigned long) 0L));
#endif
    return;
}

/* REPRODUCIBLE_BUILD will change this to TRUE */
static boolean date_via_env = FALSE;

static char *
version_string(outbuf, delim)
char *outbuf;
const char *delim;
{
    Sprintf(outbuf, "%d%s%d%s%d", VERSION_MAJOR, delim, VERSION_MINOR, delim,
            PATCHLEVEL);
#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
    Sprintf(eos(outbuf), "-%d", EDITLEVEL);
#endif
    return outbuf;
}

static char *
version_id_string(outbuf, build_date)
char *outbuf;
const char *build_date;
{
    char subbuf[64], versbuf[64];
    char betabuf[64];

#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
#if (NH_DEVEL_STATUS == NH_STATUS_BETA)
    Strcpy(betabuf, " Beta");
#else
    Strcpy(betabuf, " Work-in-progress");
#endif
#else
    betabuf[0] = '\0';
#endif

    subbuf[0] = '\0';
#ifdef PORT_SUB_ID
    subbuf[0] = ' ';
    Strcpy(&subbuf[1], PORT_SUB_ID);
#endif

    Sprintf(outbuf, "%s NetHack%s Version %s%s - last %s %s.", PORT_ID,
            subbuf, version_string(versbuf, "."), betabuf,
            date_via_env ? "revision" : "build", build_date);
    return outbuf;
}

static char *
bannerc_string(outbuf, build_date)
char *outbuf;
const char *build_date;
{
    char subbuf[64], versbuf[64];

    subbuf[0] = '\0';
#ifdef PORT_SUB_ID
    subbuf[0] = ' ';
    Strcpy(&subbuf[1], PORT_SUB_ID);
#endif
#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
#if (NH_DEVEL_STATUS == NH_STATUS_BETA)
    Strcat(subbuf, " Beta");
#else
    Strcat(subbuf, " Work-in-progress");
#endif
#endif

    Sprintf(outbuf, "         Version %s %s%s, %s %s.",
            version_string(versbuf, "."), PORT_ID, subbuf,
            date_via_env ? "revised" : "built", &build_date[4]);
#if 0
    Sprintf(outbuf, "%s NetHack%s %s Copyright 1985-%s (built %s)",
            PORT_ID, subbuf, version_string(versbuf,"."), RELEASE_YEAR,
            &build_date[4]);
#endif
    return outbuf;
}

void
do_date()
{
#ifdef KR1ED
    long clocktim = 0;
#else
    time_t clocktim = 0;
#endif
    char githash[BUFSZ], gitbranch[BUFSZ];
    char *c, cbuf[60], buf[BUFSZ];
    const char *ul_sfx;

    /* before creating date.h, make sure that xxx_GRAPHICS and
       DEFAULT_WINDOW_SYS have been set up in a viable fashion */
    windowing_sanity();

    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), INCLUDE_TEMPLATE, DATE_FILE);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    /* NB: We've moved on from SCCS, but this way this line
     * won't get clobbered when downstream projects import
     * this file into something more modern. */
    Fprintf(ofp, "%s", Dont_Edit_Code);

    (void) time(&clocktim);
#ifdef REPRODUCIBLE_BUILD
    {
        /*
         * Use date+time of latest source file revision (set up in
         * our environment rather than derived by scanning sources)
         * instead of current date+time, so that later rebuilds of
         * the same sources specifying the same configuration will
         * produce the same result.
         *
         * Changing the configuration should be done by modifying
         * config.h or <port>conf.h and setting SOURCE_DATE_EPOCH
         * based on whichever changed most recently, not by using
         *   make CFLAGS='-Dthis -Dthat'
         * to make alterations on the fly.
         *
         * Limited validation is performed to prevent dates in the
         * future (beyond a leeway of 24 hours) or distant past.
         *
         * Assumes the value of time_t is in seconds, which is
         * fundamental for Unix and mandated by POSIX.  For any ports
         * where that isn't true, leaving REPRODUCIBLE_BUILD disabled
         * is probably preferrable to hacking this code....
         */
        static struct tm nh360; /* static init should yield UTC timezone */
        unsigned long sd_num, sd_earliest, sd_latest;
        const char *sd_str = getenv("SOURCE_DATE_EPOCH");

        if (sd_str) {
            sd_num = strtoul(sd_str, (char **) 0, 10);
            /*
             * Note:  this does not need to be updated for future
             * releases.  It serves as a sanity check for potentially
             * mis-set environment, not a hard baseline for when the
             * current version could have first been built.
             */
            /* oldest date we'll accept: 7-Dec-2015 (release of 3.6.0) */
            nh360.tm_mday = 7;
            nh360.tm_mon  = 12 - 1;
            nh360.tm_year = 2015 - 1900;
            sd_earliest = (unsigned long) mktime(&nh360);
            /* 'youngest' date we'll accept: 24 hours in the future */
            sd_latest = (unsigned long) clocktim + 24L * 60L * 60L;

            if (sd_num >= sd_earliest && sd_num <= sd_latest) {
                /* use SOURCE_DATE_EPOCH value */
                clocktim = (time_t) sd_num;
                date_via_env = TRUE;
            } else {
                Fprintf(stderr, "? Invalid value for SOURCE_DATE_EPOCH (%lu)",
                        sd_num);
                if (sd_num > 0L && sd_num < sd_earliest)
                    Fprintf(stderr, ", older than %lu", sd_earliest);
                else if (sd_num > sd_latest)
                    Fprintf(stderr, ", newer than %lu", sd_latest);
                Fprintf(stderr, ".\n");
                Fprintf(stderr, ": Reverting to current date+time (%lu).\n",
                        (unsigned long) clocktim);
                (void) fflush(stderr);
            }
        } else {
            /* REPRODUCIBLE_BUILD enabled but SOURCE_DATE_EPOCH is missing */
            Fprintf(stderr, "? No value for SOURCE_DATE_EPOCH.\n");
            Fprintf(stderr, ": Using current date+time (%lu).\n",
                    (unsigned long) clocktim);
            (void) fflush(stderr);
        }
        Strcpy(cbuf, asctime(gmtime(&clocktim)));
    }
#else
    /* ordinary build: use current date+time */
    Strcpy(cbuf, ctime(&clocktim));
#endif

    if ((c = index(cbuf, '\n')) != 0)
        *c = '\0'; /* strip off the '\n' */
#ifdef NHSTDC
    ul_sfx = "UL";
#else
    ul_sfx = "L";
#endif
    if (date_via_env)
        Fprintf(ofp, "#define SOURCE_DATE_EPOCH (%lu%s) /* via getenv() */\n",
                (unsigned long) clocktim, ul_sfx);
    Fprintf(ofp, "#define BUILD_DATE \"%s\"\n", cbuf);
    if (date_via_env)
        Fprintf(ofp, "#define BUILD_TIME SOURCE_DATE_EPOCH\n");
    else
        Fprintf(ofp, "#define BUILD_TIME (%lu%s)\n",
                (unsigned long) clocktim, ul_sfx);
    Fprintf(ofp, "\n");
    Fprintf(ofp, "#define VERSION_NUMBER 0x%08lx%s\n", version.incarnation,
            ul_sfx);
    Fprintf(ofp, "#define VERSION_FEATURES 0x%08lx%s\n", version.feature_set,
            ul_sfx);
#ifdef IGNORED_FEATURES
    Fprintf(ofp, "#define IGNORED_FEATURES 0x%08lx%s\n",
            (unsigned long) IGNORED_FEATURES, ul_sfx);
#endif
    Fprintf(ofp, "#define VERSION_SANITY1 0x%08lx%s\n", version.entity_count,
            ul_sfx);
    Fprintf(ofp, "#define VERSION_SANITY2 0x%08lx%s\n", version.struct_sizes1,
            ul_sfx);
    Fprintf(ofp, "#define VERSION_SANITY3 0x%08lx%s\n", version.struct_sizes2,
            ul_sfx);
    Fprintf(ofp, "\n");
    Fprintf(ofp, "#define VERSION_STRING \"%s\"\n", version_string(buf, "."));
    Fprintf(ofp, "#define VERSION_ID \\\n \"%s\"\n",
            version_id_string(buf, cbuf));
    Fprintf(ofp, "#define COPYRIGHT_BANNER_C \\\n \"%s\"\n",
            bannerc_string(buf, cbuf));
    Fprintf(ofp, "\n");
    if (get_gitinfo(githash, gitbranch)) {
        Fprintf(ofp, "#define NETHACK_GIT_SHA \"%s\"\n", githash);
        Fprintf(ofp, "#define NETHACK_GIT_BRANCH \"%s\"\n", gitbranch);
    }
#ifdef AMIGA
    {
        struct tm *tm = localtime((time_t *) &clocktim);

        Fprintf(ofp, "#define AMIGA_VERSION_STRING ");
        Fprintf(ofp, "\"\\0$VER: NetHack %d.%d.%d (%d.%d.%d)\"\n",
                VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL,
                tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900);
    }
#endif
    Fclose(ofp);
    return;
}

static boolean
get_gitinfo(githash, gitbranch)
char *githash, *gitbranch;
{
    FILE *gifp;
    size_t len;
    char infile[MAXFNAMELEN];
    char *line, *strval, *opt, *c, *end;
    boolean havebranch = FALSE, havehash = FALSE;

    if (!githash || !gitbranch) return FALSE;

    Sprintf(infile, DATA_IN_TEMPLATE, GITINFO_FILE);
    if (!(gifp = fopen(infile, RDTMODE))) {
        /* perror(infile); */
        return FALSE;
    }

    /* read the gitinfo file */
    while ((line = fgetline(gifp)) != 0) {
        strval = index(line, '=');
        if (strval && strlen(strval) < (BUFSZ-1)) {
            opt = line;
            *strval++ = '\0';
            /* strip off the '\n' */
            if ((c = index(strval, '\n')) != 0)
                *c = '\0'; 
            if ((c = index(opt, '\n')) != 0)
                *c = '\0';
            /* strip leading and trailing white space */
            while (*strval == ' ' || *strval == '\t')
                strval++;
            end = eos(strval);
            while (--end >= strval && (*end == ' ' || *end == '\t'))
            *end = '\0';
            while (*opt == ' ' || *opt == '\t')
                opt++;
            end = eos(opt);
            while (--end >= opt && (*end == ' ' || *end == '\t'))
            *end = '\0';

            len = strlen(opt);
            if ((len >= strlen("gitbranch")) && !case_insensitive_comp(opt, "gitbranch")) {
                Strcpy(gitbranch, strval);
                havebranch = TRUE;
            }
            if ((len >= strlen("githash")) && !case_insensitive_comp(opt, "githash")) {
                Strcpy(githash, strval);
                havehash = TRUE;
            }
	}
        free(line);
    }
    Fclose(gifp);
    if (havebranch && havehash)
        return TRUE;
    return FALSE;
}

static int
case_insensitive_comp(s1, s2)
const char *s1;
const char *s2;
{
    uchar u1, u2;

    for (;; s1++, s2++) {
        u1 = (uchar) *s1;
        if (isupper(u1))
            u1 = tolower(u1);
        u2 = (uchar) *s2;
        if (isupper(u2))
            u2 = tolower(u2);
        if (u1 == '\0' || u1 != u2)
            break;
    }
    return u1 - u2;
}

static char save_bones_compat_buf[BUFSZ];

static void
build_savebones_compat_string()
{
#ifdef VERSION_COMPATIBILITY
    unsigned long uver = VERSION_COMPATIBILITY;
#endif
    Strcpy(save_bones_compat_buf,
           "save and bones files accepted from version");
#ifdef VERSION_COMPATIBILITY
    Sprintf(eos(save_bones_compat_buf), "s %lu.%lu.%lu through %d.%d.%d",
            ((uver & 0xFF000000L) >> 24), ((uver & 0x00FF0000L) >> 16),
            ((uver & 0x0000FF00L) >> 8), VERSION_MAJOR, VERSION_MINOR,
            PATCHLEVEL);
#else
    Sprintf(eos(save_bones_compat_buf), " %d.%d.%d only", VERSION_MAJOR,
            VERSION_MINOR, PATCHLEVEL);
#endif
}

static const char *build_opts[] = {
#ifdef AMIGA_WBENCH
    "Amiga WorkBench support",
#endif
#ifdef ANSI_DEFAULT
    "ANSI default terminal",
#endif
#ifdef TEXTCOLOR
    "color",
#endif
#ifdef TTY_GRAPHICS
#ifdef TTY_TILES_ESCCODES
    "console escape codes for tile hinting",
#endif
#endif
#ifdef COM_COMPL
    "command line completion",
#endif
#ifdef LIFE
    "Conway's Game of Life",
#endif
#ifdef COMPRESS
    "data file compression",
#endif
#ifdef ZLIB_COMP
    "ZLIB data file compression",
#endif
#ifdef DLB
#ifndef VERSION_IN_DLB_FILENAME
    "data librarian",
#else
    "data librarian with a version-dependent name",
#endif
#endif
#ifdef DUMPLOG
    "end-of-game dumplogs",
#endif
#ifdef HOLD_LOCKFILE_OPEN
    "exclusive lock on level 0 file",
#endif
#if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__))
    "external program as a message handler",
#endif
#ifdef MFLOPPY
    "floppy drive support",
#endif
#ifdef INSURANCE
    "insurance files for recovering from crashes",
#endif
#ifdef LOGFILE
    "log file",
#endif
#ifdef XLOGFILE
    "extended log file",
#endif
#ifdef PANICLOG
    "errors and warnings log file",
#endif
#ifdef MAIL
    "mail daemon",
#endif
#ifdef GNUDOS
    "MSDOS protected mode",
#endif
#ifdef NEWS
    "news file",
#endif
#ifdef OVERLAY
#ifdef MOVERLAY
    "MOVE overlays",
#else
#ifdef VROOMM
    "VROOMM overlays",
#else
    "overlays",
#endif
#endif
#endif
    /* pattern matching method will be substituted by nethack at run time */
    "pattern matching via :PATMATCH:",
#ifdef USE_ISAAC64
    "pseudo random numbers generated by ISAAC64",
#ifdef DEV_RANDOM
#ifdef NHSTDC
    /* include which specific one */
    "strong PRNG seed available from " DEV_RANDOM,
#else
    "strong PRNG seed available from DEV_RANDOM",
#endif
#else
#ifdef WIN32
    "strong PRNG seed available from CNG BCryptGenRandom()",
#endif
#endif  /* DEV_RANDOM */    
#else   /* ISAAC64 */
#ifdef RANDOM
    "pseudo random numbers generated by random()",
#else
    "pseudo random numbers generated by C rand()",
#endif
#endif
#ifdef SELECTSAVED
    "restore saved games via menu",
#endif
#ifdef SCORE_ON_BOTL
    "score on status line",
#endif
#ifdef CLIPPING
    "screen clipping",
#endif
#ifdef NO_TERMS
#ifdef MAC
    "screen control via mactty",
#endif
#ifdef SCREEN_BIOS
    "screen control via BIOS",
#endif
#ifdef SCREEN_DJGPPFAST
    "screen control via DJGPP fast",
#endif
#ifdef SCREEN_VGA
    "screen control via VGA graphics",
#endif
#ifdef WIN32CON
    "screen control via WIN32 console I/O",
#endif
#endif
#ifdef SHELL
    "shell command",
#endif
    "traditional status display",
#ifdef STATUS_HILITES
    "status via windowport with highlighting",
#else
    "status via windowport without highlighting",
#endif
#ifdef SUSPEND
    "suspend command",
#endif
#ifdef TTY_GRAPHICS
#ifdef TERMINFO
    "terminal info library",
#else
#if defined(TERMLIB) || (!defined(MICRO) && !defined(WIN32))
    "terminal capability library",
#endif
#endif
#endif /*TTY_GRAPHICS*/
/*#ifdef X11_GRAPHICS*/
#ifdef USE_XPM
    "tiles file in XPM format",
#endif
/*#endif*/
/*#if (defined(QT_GRAPHICS) || defined(X11_GRAPHICS)*/
#ifdef GRAPHIC_TOMBSTONE
    "graphical RIP screen",
#endif
/*#endif*/
#ifdef TIMED_DELAY
    "timed wait for display effects",
#endif
#ifdef USER_SOUNDS
    "user sounds",
#endif
#ifdef PREFIXES_IN_USE
    "variable playground",
#endif
#ifdef VISION_TABLES
    "vision tables",
#endif
#ifdef ZEROCOMP
    "zero-compressed save files",
#endif
#ifdef RLECOMP
    "run-length compression of map in save files",
#endif
#ifdef SYSCF
    "system configuration at run-time",
#endif
    save_bones_compat_buf,
    "and basic NetHack features"
};

struct win_info {
    const char *id, /* DEFAULT_WINDOW_SYS string */
        *name;      /* description, often same as id */
};
static struct win_info window_opts[] = {
#ifdef TTY_GRAPHICS
    { "tty",
      /* testing 'USE_TILES' here would bring confusion because it could
         apply to another interface such as X11, so check MSDOS explicitly
         instead; even checking TTY_TILES_ESCCODES would probably be
         confusing to most users (and it will already be listed separately
         in the compiled options section so users aware of it can find it) */
#ifdef MSDOS
      "traditional text with optional 'tiles' graphics"
#else
      /* assume that one or more of IBMgraphics, DECgraphics, or MACgraphics
         can be enabled; we can't tell from here whether that is accurate */
      "traditional text with optional line-drawing"
#endif
    },
#endif
#ifdef CURSES_GRAPHICS
    { "curses", "terminal-based graphics" },
#endif
#ifdef X11_GRAPHICS
    { "X11", "X11" },
#endif
#ifdef QT_GRAPHICS /* too vague; there are multiple incompatible versions */
    { "Qt", "Qt" },
#endif
#ifdef GNOME_GRAPHICS /* unmaintained/defunct */
    { "Gnome", "Gnome" },
#endif
#ifdef MAC /* defunct OS 9 interface */
    { "mac", "Mac" },
#endif
#ifdef AMIGA_INTUITION /* unmaintained/defunct */
    { "amii", "Amiga Intuition" },
#endif
#ifdef GEM_GRAPHICS /* defunct Atari interface */
    { "Gem", "Gem" },
#endif
#ifdef MSWIN_GRAPHICS /* win32 */
    { "mswin", "mswin" },
#endif
#ifdef BEOS_GRAPHICS /* unmaintained/defunct */
    { "BeOS", "BeOS InterfaceKit" },
#endif
    { 0, 0 }
};

static void
windowing_sanity()
{
#ifndef DEFAULT_WINDOW_SYS
    /* pre-standard compilers didn't support #error; wait til run-time */
    Fprintf(stderr,
            "Configuration error: DEFAULT_WINDOW_SYS is not defined.\n");
    exit(EXIT_FAILURE);
/*NOTREACHED*/

/* put in a dummy value so that do_options() will compile and makedefs
   will build, otherwise the message above won't ever get delivered */
#define DEFAULT_WINDOW_SYS "<undefined>"
#else  /*DEFAULT_WINDOW_SYS*/

    if (!window_opts[0].id) {
        Fprintf(stderr, "Configuration error: no windowing systems "
                        "(TTY_GRAPHICS, &c) enabled.\n");
        exit(EXIT_FAILURE);
    }

    {
        int i;

        for (i = 0; window_opts[i].id; ++i)
            if (!strcmp(window_opts[i].id, DEFAULT_WINDOW_SYS))
                break;
        if (!window_opts[i].id) { /* went through whole list without a match */
            Fprintf(stderr, "Configuration error: DEFAULT_WINDOW_SYS (%s)\n",
                    DEFAULT_WINDOW_SYS);
            Fprintf(stderr,
                    " does not match any enabled windowing system (%s%s).\n",
                    window_opts[0].id, window_opts[1].id ? ", &c" : "");
            exit(EXIT_FAILURE);
        }
    }
#endif /*DEFAULT_WINDOW_SYS*/
}

static const char opt_indent[] = "    ";

static void
opt_out_words(str, length_p)
char *str; /* input, but modified during processing */
int *length_p; /* in/out */
{
    char *word;

    while (*str) {
        word = index(str, ' ');
#if 0
        /* treat " (" as unbreakable space */
        if (word && *(word + 1) == '(')
            word = index(word + 1,  ' ');
#endif
        if (word)
            *word = '\0';
        if (*length_p + (int) strlen(str) > COLNO - 5)
            Fprintf(ofp, "\n%s", opt_indent),
                *length_p = (int) strlen(opt_indent);
        else
            Fprintf(ofp, " "), (*length_p)++;
        Fprintf(ofp, "%s", str), *length_p += (int) strlen(str);
        str += strlen(str) + (word ? 1 : 0);
    }
}

void
do_options()
{
    char buf[BUFSZ];
    int i, length, winsyscnt;

    windowing_sanity();

    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), DATA_TEMPLATE, OPTIONS_FILE);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }

    build_savebones_compat_string();
    Fprintf(ofp, "\n%sNetHack version %d.%d.%d%s\n",
            opt_indent,
            VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL,
#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
#if (NH_DEVEL_STATUS == NH_STATUS_BETA)
            " [beta]"
#else
            " [work-in-progress]"
#endif
#else
            ""
#endif
            );

    Fprintf(ofp, "\nOptions compiled into this edition:\n");
    length = COLNO + 1; /* force 1st item onto new line */
    for (i = 0; i < SIZE(build_opts); i++) {
        opt_out_words(strcat(strcpy(buf, build_opts[i]),
                             (i < SIZE(build_opts) - 1) ? "," : "."),
                      &length);
    }
    Fprintf(ofp, "\n"); /* terminate last line of words */

    winsyscnt = SIZE(window_opts) - 1;
    Fprintf(ofp, "\nSupported windowing system%s:\n",
            (winsyscnt > 1) ? "s" : "");
    length = COLNO + 1; /* force 1st item onto new line */
    for (i = 0; i < winsyscnt; i++) {
        Sprintf(buf, "\"%s\"", window_opts[i].id);
        if (strcmp(window_opts[i].name, window_opts[i].id))
            Sprintf(eos(buf), " (%s)", window_opts[i].name);
        /*
         * 1 : foo.
         * 2 : foo and bar  (note no period; comes from 'with default' below)
         * 3+: for, bar, and quux
         */
        opt_out_words(strcat(buf, (winsyscnt == 1) ? "." /* no 'default' */
                                  : (winsyscnt == 2 && i == 0) ? " and"
                                    : (i == winsyscnt - 2) ? ", and"
                                      : ","),
                      &length);
    }
    if (winsyscnt > 1) {
        Sprintf(buf, "with a default of \"%s\".", DEFAULT_WINDOW_SYS);
        opt_out_words(buf, &length);
    }
    Fprintf(ofp, "\n"); /* terminate last line of words */

    /* end with a blank line */
    Fprintf(ofp, "\n");
    Fclose(ofp);
    return;
}

/* routine to decide whether to discard something from data.base */
static boolean
d_filter(line)
char *line;
{
    if (*line == '#')
        return TRUE; /* ignore comment lines */
    return FALSE;
}

/*
 *
     New format (v3.1) of 'data' file which allows much faster lookups [pr]
"do not edit"           first record is a comment line
01234567                hexadecimal formatted offset to text area
name-a                  first name of interest
123,4                   offset to name's text, and number of lines for it
name-b                  next name of interest
name-c                  multiple names which share same description also
456,7                   share a single offset,count line
.                       sentinel to mark end of names
789,0                   dummy record containing offset, count of EOF
text-a                  4 lines of descriptive text for name-a
text-a                  at file position 0x01234567L + 123L
text-a
text-a
text-b/text-c           7 lines of text for names-b and -c
text-b/text-c           at fseek(0x01234567L + 456L)
...
 *
 */

void
do_data()
{
    char infile[60], tempfile[60];
    boolean ok;
    long txt_offset;
    int entry_cnt, line_cnt;
    char *line;

    Sprintf(tempfile, DATA_TEMPLATE, "database.tmp");
    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), DATA_TEMPLATE, DATA_FILE);
    Sprintf(infile, DATA_IN_TEMPLATE, DATA_FILE);
#ifdef SHORT_FILENAMES
    Strcat(infile, ".bas");
#else
    Strcat(infile, ".base");
#endif
    if (!(ifp = fopen(infile, RDTMODE))) { /* data.base */
        perror(infile);
        exit(EXIT_FAILURE);
    }
    if (!(ofp = fopen(filename, WRTMODE))) { /* data */
        perror(filename);
        Fclose(ifp);
        exit(EXIT_FAILURE);
    }
    if (!(tfp = fopen(tempfile, WRTMODE))) { /* database.tmp */
        perror(tempfile);
        Fclose(ifp);
        Fclose(ofp);
        Unlink(filename);
        exit(EXIT_FAILURE);
    }

    /* output a dummy header record; we'll rewind and overwrite it later */
    Fprintf(ofp, "%s%08lx\n", Dont_Edit_Data, 0L);

    entry_cnt = line_cnt = 0;
    /* read through the input file and split it into two sections */
    while ((line = fgetline(ifp)) != 0) {
        if (d_filter(line)) {
            free(line);
            continue;
        }
        if (*line > ' ' || *line < 0) { /* got an entry name */
            /* first finish previous entry */
            if (line_cnt)
                Fprintf(ofp, "%d\n", line_cnt), line_cnt = 0;
            /* output the entry name */
            (void) fputs(line, ofp);
            entry_cnt++;        /* update number of entries */
        } else if (entry_cnt) { /* got some descriptive text */
            /* update previous entry with current text offset */
            if (!line_cnt)
                Fprintf(ofp, "%ld,", ftell(tfp));
            /* save the text line in the scratch file */
            (void) fputs(line, tfp);
            line_cnt++; /* update line counter */
        }
        free(line);
    }
    /* output an end marker and then record the current position */
    if (line_cnt)
        Fprintf(ofp, "%d\n", line_cnt);
    Fprintf(ofp, ".\n%ld,%d\n", ftell(tfp), 0);
    txt_offset = ftell(ofp);
    Fclose(ifp); /* all done with original input file */

    /* reprocess the scratch file; 1st format an error msg, just in case */
    line = malloc(BUFSZ + MAXFNAMELEN);
    Sprintf(line, "rewind of \"%s\"", tempfile);
    if (rewind(tfp) != 0)
        goto dead_data;
    free(line);
    /* copy all lines of text from the scratch file into the output file */
    while ((line = fgetline(tfp)) != 0) {
        (void) fputs(line, ofp);
        free(line);
    }

    /* finished with scratch file */
    Fclose(tfp);
    Unlink(tempfile); /* remove it */

    /* update the first record of the output file; prepare error msg 1st */
    line = malloc(BUFSZ + MAXFNAMELEN);
    Sprintf(line, "rewind of \"%s\"", filename);
    ok = (rewind(ofp) == 0);
    if (ok) {
        Sprintf(line, "header rewrite of \"%s\"", filename);
        ok = (fprintf(ofp, "%s%08lx\n", Dont_Edit_Data,
                      (unsigned long) txt_offset) >= 0);
    }
    if (!ok) {
    dead_data:
        perror(line); /* report the problem */
        free(line);
        /* close and kill the aborted output file, then give up */
        Fclose(ofp);
        Unlink(filename);
        exit(EXIT_FAILURE);
    }
    free(line);

    /* all done */
    Fclose(ofp);

    return;
}

/* routine to decide whether to discard something from oracles.txt */
static boolean
h_filter(line)
char *line;
{
    static boolean skip = FALSE;
    char *tag;

    SpinCursor(3);

    if (*line == '#')
        return TRUE; /* ignore comment lines */

    tag = malloc(strlen(line));
    if (sscanf(line, "----- %s", tag) == 1) {
        skip = FALSE;
    } else if (skip && !strncmp(line, "-----", 5))
        skip = FALSE;
    free(tag);
    return skip;
}

static const char *special_oracle[] = {
    "\"...it is rather disconcerting to be confronted with the",
    "following theorem from [Baker, Gill, and Solovay, 1975].", "",
    "Theorem 7.18  There exist recursive languages A and B such that",
    "  (1)  P(A) == NP(A), and", "  (2)  P(B) != NP(B)", "",
    "This provides impressive evidence that the techniques that are",
    "currently available will not suffice for proving that P != NP or        "
    "  ",
    "that P == NP.\"  [Garey and Johnson, p. 185.]"
};

/*
   The oracle file consists of a "do not edit" comment, a decimal count N
   and set of N+1 hexadecimal fseek offsets, followed by N multiple-line
   records, separated by "---" lines.  The first oracle is a special case.
   The input data contains just those multi-line records, separated by
   "-----" lines.
 */

void
do_oracles()
{
    char infile[60], tempfile[60];
    boolean in_oracle, ok;
    long fpos;
    unsigned long txt_offset, offset;
    int oracle_cnt;
    register int i;
    char *line;

    Sprintf(tempfile, DATA_TEMPLATE, "oracles.tmp");
    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), DATA_TEMPLATE, ORACLE_FILE);
    Sprintf(infile, DATA_IN_TEMPLATE, ORACLE_FILE);
    Strcat(infile, ".txt");
    if (!(ifp = fopen(infile, RDTMODE))) {
        perror(infile);
        exit(EXIT_FAILURE);
    }
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        Fclose(ifp);
        exit(EXIT_FAILURE);
    }
    if (!(tfp = fopen(tempfile, WRTMODE))) { /* oracles.tmp */
        perror(tempfile);
        Fclose(ifp);
        Fclose(ofp);
        Unlink(filename);
        exit(EXIT_FAILURE);
    }

    /* output a dummy header record; we'll rewind and overwrite it later */
    Fprintf(ofp, "%s%5d\n", Dont_Edit_Data, 0);

    /* handle special oracle; it must come first */
    (void) fputs("---\n", tfp);
    offset = (unsigned long) ftell(tfp);
    Fprintf(ofp, "%05lx\n", offset); /* start pos of special oracle */
    for (i = 0; i < SIZE(special_oracle); i++) {
        (void) fputs(xcrypt(special_oracle[i]), tfp);
        (void) fputc('\n', tfp);
    }
    SpinCursor(3);

    oracle_cnt = 1;
    (void) fputs("---\n", tfp);
    offset = (unsigned long) ftell(tfp);
    Fprintf(ofp, "%05lx\n", offset); /* start pos of first oracle */
    in_oracle = FALSE;

    while ((line = fgetline(ifp)) != 0) {
        SpinCursor(3);

        if (h_filter(line)) {
            free(line);
            continue;
        }
        if (!strncmp(line, "-----", 5)) {
            if (!in_oracle) {
                free(line);
                continue;
            }
            in_oracle = FALSE;
            oracle_cnt++;
            (void) fputs("---\n", tfp);
            offset = (unsigned long) ftell(tfp);
            Fprintf(ofp, "%05lx\n", offset); /* start pos of this oracle */
        } else {
            in_oracle = TRUE;
            (void) fputs(xcrypt(line), tfp);
        }
        free(line);
    }

    if (in_oracle) { /* need to terminate last oracle */
        oracle_cnt++;
        (void) fputs("---\n", tfp);
        offset = (unsigned long) ftell(tfp);
        Fprintf(ofp, "%05lx\n", offset); /* eof position */
    }

    /* record the current position */
    txt_offset = (unsigned long) ftell(ofp);
    Fclose(ifp); /* all done with original input file */

    /* reprocess the scratch file; 1st format an error msg, just in case */
    line = malloc(BUFSZ + MAXFNAMELEN);
    Sprintf(line, "rewind of \"%s\"", tempfile);
    if (rewind(tfp) != 0)
        goto dead_data;
    free(line);
    /* copy all lines of text from the scratch file into the output file */
    while ((line = fgetline(tfp)) != 0) {
        (void) fputs(line, ofp);
        free(line);
    }

    /* finished with scratch file */
    Fclose(tfp);
    Unlink(tempfile); /* remove it */

    /* update the first record of the output file; prepare error msg 1st */
    line = malloc(BUFSZ + MAXFNAMELEN);
    Sprintf(line, "rewind of \"%s\"", filename);
    ok = (rewind(ofp) == 0);
    if (ok) {
        Sprintf(line, "header rewrite of \"%s\"", filename);
        ok = (fprintf(ofp, "%s%5d\n", Dont_Edit_Data, oracle_cnt) >= 0);
    }
    if (ok) {
        Sprintf(line, "data rewrite of \"%s\"", filename);
        for (i = 0; i <= oracle_cnt; i++) {
#ifndef VMS /* alpha/vms v1.0; this fflush seems to confuse ftell */
            if (!(ok = (fflush(ofp) == 0)))
                break;
#endif
            if (!(ok = (fpos = ftell(ofp)) >= 0))
                break;
            if (!(ok = (fseek(ofp, fpos, SEEK_SET) >= 0)))
                break;
            if (!(ok = (fscanf(ofp, "%5lx", &offset) == 1)))
                break;
#ifdef MAC
#ifdef __MWERKS__
            /*
            MetroWerks CodeWarrior Pro 1's (AKA CW12) version of MSL
            (ANSI C Libraries) needs this rewind or else the fprintf
            stops working.  This may also be true for CW11, but has
            never been checked.
            */
            rewind(ofp);
#endif
#endif
            if (!(ok = (fseek(ofp, fpos, SEEK_SET) >= 0)))
                break;
            offset += txt_offset;
            if (!(ok = (fprintf(ofp, "%05lx\n", offset) >= 0)))
                break;
        }
    }
    if (!ok) {
    dead_data:
        perror(line); /* report the problem */
        free(line);
        /* close and kill the aborted output file, then give up */
        Fclose(ofp);
        Unlink(filename);
        exit(EXIT_FAILURE);
    }
    free(line);

    /* all done */
    Fclose(ofp);

    return;
}

void
do_dungeon()
{
    char *line;

    Sprintf(filename, DATA_IN_TEMPLATE, DGN_I_FILE);
    if (!(ifp = fopen(filename, RDTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), DGN_TEMPLATE, DGN_O_FILE);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp, "%s", Dont_Edit_Data);

    tfp = getfp(DATA_TEMPLATE, "grep.tmp", WRTMODE);
    grep0(ifp, tfp);
    ifp = getfp(DATA_TEMPLATE, "grep.tmp", RDTMODE);

    while ((line = fgetline(ifp)) != 0) {
        SpinCursor(3);

        if (line[0] == '#') {
            free(line);
            continue; /* discard comments */
        }
        (void) fputs(line, ofp);
        free(line);
    }
    Fclose(ifp);
    Fclose(ofp);

    delete_file(DATA_TEMPLATE, "grep.tmp");
    return;
}

void
do_monstr()
{
    /* Don't break anything for ports that haven't been updated. */
    printf("DEPRECATION WARNINGS:\n");
    printf("'makedefs -m' is deprecated.  Remove all references\n");
    printf("  to it from the build process.\n");
    printf("'monstr.c' is deprecated.  Remove all references to\n");
    printf("  it from the build process.\n");
    printf("monstr[] is deprecated.  Replace monstr[x] with\n");
    printf("  mons[x].difficulty\n");
    printf("monstr_init() is deprecated.  Remove all references to it.\n");

    /*
     * create the source file, "monstr.c"
     */
    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), SOURCE_TEMPLATE, MON_STR_C);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp, "%s", Dont_Edit_Code);
    Fprintf(ofp, "#include \"config.h\"\n");
    Fprintf(ofp, "\nconst int monstrXXX[] = {\n");
    Fprintf(ofp, "0};\n");
    Fprintf(ofp, "/*\n");
    Fprintf(ofp, "DEPRECATION WARNINGS:\n");
    Fprintf(ofp, "'makedefs -m' is deprecated.  Remove all references\n");
    Fprintf(ofp, "  to it from the build process.\n");
    Fprintf(ofp, "'monstr.c' is deprecated.  Remove all references to\n");
    Fprintf(ofp, "  it from the build process.\n");
    Fprintf(ofp, "monstr[] is deprecated.  Replace monstr[x] with\n");
    Fprintf(ofp, "  mons[x].difficulty\n");
    Fprintf(ofp, "monstr_init() is deprecated.  Remove all references to it.\n");
    Fprintf(ofp, "*/\n");

    Fprintf(ofp, "\nvoid NDECL(monstr_init);\n");
    Fprintf(ofp, "\nvoid\n");
    Fprintf(ofp, "monstr_init()\n");
    Fprintf(ofp, "{\n");
    Fprintf(ofp, "    return;\n");
    Fprintf(ofp, "}\n");
    Fprintf(ofp, "\n/*monstr.c*/\n");

    Fclose(ofp);
    return;
}

void
do_permonst()
{
    int i;
    char *c, *nam;

    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), INCLUDE_TEMPLATE, MONST_FILE);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp, "%s", Dont_Edit_Code);
    Fprintf(ofp, "#ifndef PM_H\n#define PM_H\n");

    if (use_enum) {
        Fprintf(ofp, "\nenum monnums {");
#if 0
        /* need #define ENUM_PM for the full NetHack build to include these */
        Fprintf(ofp, "\n        NON_PM = -1,");
        Fprintf(ofp, "\n        LOW_PM = 0,");
#endif
    }
    for (i = 0; mons[i].mlet; i++) {
        SpinCursor(3);
        if (use_enum)
            Fprintf(ofp, "\n        PM_");
        else
            Fprintf(ofp, "\n#define\tPM_");
        if (mons[i].mlet == S_HUMAN && !strncmp(mons[i].ename, "were", 4))
            Fprintf(ofp, "HUMAN_");
        for (nam = c = tmpdup(mons[i].ename); *c; c++)
            if (*c >= 'a' && *c <= 'z')
                *c -= (char) ('a' - 'A');
            else if (*c < 'A' || *c > 'Z')
                *c = '_';
        if (use_enum)
            Fprintf(ofp, "%s = %d,", nam, i);
        else
            Fprintf(ofp, "%s\t%d", nam, i);
    }
    if (use_enum) {
        Fprintf(ofp, "\n\n        NUMMONS = %d", i);
        Fprintf(ofp, "\n};\n");
    } else {
        Fprintf(ofp, "\n\n#define\tNUMMONS\t%d\n", i);
    }
    Fprintf(ofp, "\n#endif /* PM_H */\n");
    Fclose(ofp);
    return;
}

/*      Start of Quest text file processing. */
#include "qtext.h"

static struct qthdr qt_hdr;
static struct msghdr msg_hdr[N_HDR];
static struct qtmsg *curr_msg;

static int qt_line;

static boolean in_msg;
#define NO_MSG 1 /* strlen of a null line returned by fgets() */

static boolean
qt_comment(s)
char *s;
{
    if (s[0] == '#')
        return  TRUE;
    return (boolean) (!in_msg && strlen(s) == NO_MSG);
}

static boolean
qt_control(s)
char *s;
{
    return (boolean) (s[0] == '%' && (s[1] == 'C' || s[1] == 'E'));
}

static int
get_hdr(code)
char *code;
{
    int i;

    for (i = 0; i < qt_hdr.n_hdr; i++)
        if (!strncmp(code, qt_hdr.id[i], LEN_HDR))
            return ++i;

    return 0;
}

static boolean
new_id(code)
char *code;
{
    if (qt_hdr.n_hdr >= N_HDR) {
        Fprintf(stderr, OUT_OF_HEADERS, qt_line);
        return FALSE;
    }

    strncpy(&qt_hdr.id[qt_hdr.n_hdr][0], code, LEN_HDR);
    msg_hdr[qt_hdr.n_hdr].n_msg = 0;
    qt_hdr.offset[qt_hdr.n_hdr++] = 0L;
    return TRUE;
}

static boolean
known_msg(num, id)
int num, id;
{
    int i;

    for (i = 0; i < msg_hdr[num].n_msg; i++)
        if (msg_hdr[num].qt_msg[i].msgnum == id)
            return TRUE;

    return FALSE;
}

static void
new_msg(s, num, id)
char *s;
int num, id;
{
    struct qtmsg *qt_msg;

    if (msg_hdr[num].n_msg >= N_MSG) {
        Fprintf(stderr, OUT_OF_MESSAGES, qt_line);
    } else {
        qt_msg = &(msg_hdr[num].qt_msg[msg_hdr[num].n_msg++]);
        qt_msg->msgnum = id;
        qt_msg->delivery = s[2];
        qt_msg->offset = qt_msg->size = qt_msg->summary_size = 0L;

        curr_msg = qt_msg;
    }
}

/* check %E record for "[summary text]" that nethack can stuff into the
   message history buffer when delivering text via window instead of pline */
static char *
valid_qt_summary(s, parsing)
char *s;         /* end record: "%E" optionally followed by " [summary]" */
boolean parsing; /* curr_msg is valid iff this is True */
{
    static char summary[BUFSZ];
    char *p;

    if (*s != '%' || *(s + 1) != 'E')
        return (char *) 0;
    if ((p = index(s, '[')) == 0)
        return (char *) 0;
    /* note: opening '[' and closing ']' will be retained in the output;
       anything after ']' will be discarded by putting a newline there */
    Strcpy(summary, p);

    /* have an opening bracket; summary[] holds it and all text that follows
     */
    p = eos(summary);
    /* find closing bracket */
    while (p > summary && *(p - 1) != ']')
        --p;

    if (p == summary) {
        /* we backed up all the way to the start without finding a bracket */
        if (parsing) /* malformed summary */
            Fprintf(stderr, MAL_SUM, qt_line);
    } else if (p == summary + 1) {
        ;    /* ignore empty [] */
    } else { /* got something */
             /* p points one spot past ']', usually to '\n';
                we need to include the \n as part of the size */
        if (parsing) {
            /* during the writing pass we won't be able to recheck
               delivery, so any useless summary for a pline mode
               message has to be carried along to the output file */
            if (curr_msg->delivery == 'p')
                Fprintf(stderr, DUMB_SUM, qt_line);
            /* +1 is for terminating newline */
            curr_msg->summary_size = (long) (p - summary) + 1L;
        } else {
            /* caller is writing rather than just parsing;
               force newline after the closing bracket */
            Strcpy(p, "\n");
        }
        return summary;
    }
    return (char *) 0;
}

static void
do_qt_control(s)
char *s;
{
    char code[BUFSZ];
    int num, id = 0;

    if (!index(s, '\n'))
        Fprintf(stderr, CTRL_TRUNC, qt_line);

    switch (s[1]) {
    case 'C':
        if (in_msg) {
            Fprintf(stderr, CREC_IN_MSG, qt_line);
            break;
        } else {
            in_msg = TRUE;
            if (sscanf(&s[4], "%s %5d", code, &id) != 2) {
                Fprintf(stderr, UNREC_CREC, qt_line);
                break;
            }
            num = get_hdr(code);
            if (!num && !new_id(code))
                break;
            num = get_hdr(code) - 1;
            if (known_msg(num, id))
                Fprintf(stderr, DUP_MSG, qt_line);
            else
                new_msg(s, num, id);
        }
        break;

    case 'E':
        if (!in_msg) {
            Fprintf(stderr, END_NOT_IN_MSG, qt_line);
        } else {
            /* sets curr_msg->summary_size if applicable */
            (void) valid_qt_summary(s, TRUE);
            in_msg = FALSE;
        }
        break;

    default:
        Fprintf(stderr, UNREC_CREC, qt_line);
        break;
    }
}

static void
do_qt_text(s)
char *s;
{
    if (!in_msg) {
        Fprintf(stderr, TEXT_NOT_IN_MSG, qt_line);
    } else if (!index(s, '\n')) {
        Fprintf(stderr, TEXT_TRUNC, qt_line);
    }

    curr_msg->size += strlen(s);
    return;
}

static void
adjust_qt_hdrs()
{
    int i, j;
    long count = 0L, hdr_offset = sizeof(int)
                                  + (sizeof(char) * LEN_HDR + sizeof(long))
                                        * qt_hdr.n_hdr;

    for (i = 0; i < qt_hdr.n_hdr; i++) {
        qt_hdr.offset[i] = hdr_offset;
        hdr_offset += sizeof(int) + sizeof(struct qtmsg) * msg_hdr[i].n_msg;
    }

    for (i = 0; i < qt_hdr.n_hdr; i++)
        for (j = 0; j < msg_hdr[i].n_msg; j++) {
            msg_hdr[i].qt_msg[j].offset = hdr_offset + count;
            count +=
                msg_hdr[i].qt_msg[j].size + msg_hdr[i].qt_msg[j].summary_size;
        }
    return;
}

static void
put_qt_hdrs()
{
    int i;

    /*
     *  The main header record.
     */
    if (debug)
        Fprintf(stderr, "%ld: header info.\n", ftell(ofp));
    (void) fwrite((genericptr_t) & (qt_hdr.n_hdr), sizeof(int), 1, ofp);
    (void) fwrite((genericptr_t) & (qt_hdr.id[0][0]), sizeof(char) * LEN_HDR,
                  qt_hdr.n_hdr, ofp);
    (void) fwrite((genericptr_t) & (qt_hdr.offset[0]), sizeof(long),
                  qt_hdr.n_hdr, ofp);
    if (debug) {
        for (i = 0; i < qt_hdr.n_hdr; i++)
            Fprintf(stderr, "%s @ %ld, ", qt_hdr.id[i], qt_hdr.offset[i]);
        Fprintf(stderr, "\n");
    }

    /*
     *  The individual class headers.
     */
    for (i = 0; i < qt_hdr.n_hdr; i++) {
        if (debug)
            Fprintf(stderr, "%ld: %s header info.\n", ftell(ofp),
                    qt_hdr.id[i]);
        (void) fwrite((genericptr_t) & (msg_hdr[i].n_msg), sizeof(int), 1,
                      ofp);
        (void) fwrite((genericptr_t) & (msg_hdr[i].qt_msg[0]),
                      sizeof(struct qtmsg), msg_hdr[i].n_msg, ofp);
        if (debug) {
            int j;

            for (j = 0; j < msg_hdr[i].n_msg; j++) {
                Fprintf(stderr, "msg %d @ %ld (%ld)",
                        msg_hdr[i].qt_msg[j].msgnum,
                        msg_hdr[i].qt_msg[j].offset,
                        msg_hdr[i].qt_msg[j].size);
                if (msg_hdr[i].qt_msg[j].summary_size)
                    Fprintf(stderr, " [%ld]",
                            msg_hdr[i].qt_msg[j].summary_size);
                Fprintf(stderr, "\n");
            }
        }
    }
}

void
do_questtxt()
{
    char *line;

    Sprintf(filename, DATA_IN_TEMPLATE, QTXT_I_FILE);
    if (!(ifp = fopen(filename, RDTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }

    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), DATA_TEMPLATE, QTXT_O_FILE);
    if (!(ofp = fopen(filename, WRBMODE))) {
        perror(filename);
        Fclose(ifp);
        exit(EXIT_FAILURE);
    }

    qt_hdr.n_hdr = 0;
    qt_line = 0;
    in_msg = FALSE;

    while ((line = fgetline(ifp)) != 0) {
        SpinCursor(3);

        qt_line++;
        if (qt_control(line))
            do_qt_control(line);
        else if (qt_comment(line)) {
            free(line);
            continue;
        } else
            do_qt_text(line);
        free(line);
    }

    (void) rewind(ifp);
    in_msg = FALSE;
    adjust_qt_hdrs();
    put_qt_hdrs();
    while ((line = fgetline(ifp)) != 0) {
        if (qt_control(line)) {
            char *summary_p = 0;

            in_msg = (line[1] == 'C');
            if (!in_msg)
                summary_p = valid_qt_summary(line, FALSE);
            /* don't write anything unless we've got a summary */
            if (!summary_p) {
                free(line);
                continue;
            }
            /* we have summary text; replace raw %E record with it */
            Strcpy(line, summary_p); /* (guaranteed to fit) */
        } else if (qt_comment(line)) {
            free(line);
            continue;
        }
        if (debug)
            Fprintf(stderr, "%ld: %s", ftell(stdout), line);
        (void) fputs(xcrypt(line), ofp);
        free(line);
    }
    Fclose(ifp);
    Fclose(ofp);
    return;
}

static char temp[32];

static char *limit(name, pref) /* limit a name to 30 characters length */
char *name;
int pref;
{
    (void) strncpy(temp, name, pref ? 26 : 30);
    temp[pref ? 26 : 30] = 0;
    return temp;
}

void
do_objs()
{
    int i, sum = 0;
    char *c, *objnam;
    int nspell = 0;
    int prefix = 0;
    char class = '\0';
    boolean sumerr = FALSE;

    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), INCLUDE_TEMPLATE, ONAME_FILE);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp, "%s", Dont_Edit_Code);
    Fprintf(ofp, "#ifndef ONAMES_H\n#define ONAMES_H\n\n");

    for (i = 0; !i || objects[i].oc_class != ILLOBJ_CLASS; i++) {
        SpinCursor(3);

        objects[i].oc_name_idx = objects[i].oc_descr_idx = i; /* init */
        if (!(objnam = tmpdup(OBJ_ENAME(objects[i]))))
            continue;

        /* make sure probabilities add up to 1000 */
        if (objects[i].oc_class != class) {
            if (sum && sum != 1000) {
                Fprintf(stderr, "prob error for class %d (%d%%)", class, sum);
                (void) fflush(stderr);
                sumerr = TRUE;
            }
            class = objects[i].oc_class;
            sum = 0;
        }

        for (c = objnam; *c; c++)
            if (*c >= 'a' && *c <= 'z')
                *c -= (char) ('a' - 'A');
            else if (*c < 'A' || *c > 'Z')
                *c = '_';

        switch (class) {
        case WAND_CLASS:
            Fprintf(ofp, "#define\tWAN_");
            prefix = 1;
            break;
        case RING_CLASS:
            Fprintf(ofp, "#define\tRIN_");
            prefix = 1;
            break;
        case POTION_CLASS:
            Fprintf(ofp, "#define\tPOT_");
            prefix = 1;
            break;
        case SPBOOK_CLASS:
            Fprintf(ofp, "#define\tSPE_");
            prefix = 1;
            nspell++;
            break;
        case SCROLL_CLASS:
            Fprintf(ofp, "#define\tSCR_");
            prefix = 1;
            break;
        case AMULET_CLASS:
            /* avoid trouble with stupid C preprocessors */
            Fprintf(ofp, "#define\t");
            if (objects[i].oc_material == PLASTIC) {
                Fprintf(ofp, "FAKE_AMULET_OF_YENDOR\t%d\n", i);
                prefix = -1;
                break;
            }
            break;
        case GEM_CLASS:
            /* avoid trouble with stupid C preprocessors */
            if (objects[i].oc_material == GLASS) {
                Fprintf(ofp, "/* #define\t%s\t%d */\n", objnam, i);
                prefix = -1;
                break;
            }
            /*FALLTHRU*/
        default:
            Fprintf(ofp, "#define\t");
        }
        if (prefix >= 0)
            Fprintf(ofp, "%s\t%d\n", limit(objnam, prefix), i);
        prefix = 0;

        sum += objects[i].oc_prob;
    }

    /* check last set of probabilities */
    if (sum && sum != 1000) {
        Fprintf(stderr, "prob error for class %d (%d%%)", class, sum);
        (void) fflush(stderr);
        sumerr = TRUE;
    }

    Fprintf(ofp, "#define\tLAST_GEM\t(JADE)\n");
    Fprintf(ofp, "#define\tMAXSPELL\t%d\n", nspell + 1);
    Fprintf(ofp, "#define\tNUM_OBJECTS\t%d\n", i);

    Fprintf(ofp, "\n/* Artifacts (unique objects) */\n\n");

    for (i = 1; artifact_names[i]; i++) {
        SpinCursor(3);

        for (c = objnam = tmpdup(artifact_names[i]); *c; c++)
            if (*c >= 'a' && *c <= 'z')
                *c -= (char) ('a' - 'A');
            else if (*c < 'A' || *c > 'Z')
                *c = '_';

        if (!strncmp(objnam, "THE_", 4))
            objnam += 4;
        /* fudge _platinum_ YENDORIAN EXPRESS CARD */
        if (!strncmp(objnam, "PLATINUM_", 9))
            objnam += 9;
        Fprintf(ofp, "#define\tART_%s\t%d\n", limit(objnam, 1), i);
    }

    Fprintf(ofp, "#define\tNROFARTIFACTS\t%d\n", i - 1);
    Fprintf(ofp, "\n#endif /* ONAMES_H */\n");
    Fclose(ofp);
    if (sumerr)
        exit(EXIT_FAILURE);
    return;
}

/* Read one line from input, up to and including the next newline
 * character. Returns a pointer to the heap-allocated string, or a
 * null pointer if no characters were read.
 */
static char *
fgetline(fd)
FILE *fd;
{
    static const int inc = 256;
    int len = inc;
    char *c = malloc(len), *ret;

    for (;;) {
        ret = fgets(c + len - inc, inc, fd);
        if (!ret) {
            free(c);
            c = NULL;
            break;
        } else if (index(c, '\n')) {
            /* normal case: we have a full line */
            break;
        }
        len += inc;
        c = realloc(c, len);
    }
    return c;
}

static char *
tmpdup(str)
const char *str;
{
    static char buf[128];

    if (!str)
        return (char *) 0;
    (void) strncpy(buf, str, 127);
    return buf;
}

static char *
eos(str)
char *str;
{
    while (*str)
        str++;
    return str;
}

/*
 * macro used to control vision algorithms:
 *      VISION_TABLES => generate tables
 */

void
do_vision()
{
#ifdef VISION_TABLES
    int i, j;

    /* Everything is clear.  xclear may be malloc'ed.
     * Block the upper left corner (BLOCK_HEIGHTxBLOCK_WIDTH)
     */
    for (i = 0; i < MAX_ROW; i++)
        for (j = 0; j < MAX_COL; j++)
            if (i < BLOCK_HEIGHT && j < BLOCK_WIDTH)
                xclear[i][j] = '\000';
            else
                xclear[i][j] = '\001';
#endif /* VISION_TABLES */

    SpinCursor(3);

    /*
     * create the include file, "vis_tab.h"
     */
    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), INCLUDE_TEMPLATE, VIS_TAB_H);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp, "%s", Dont_Edit_Code);
    Fprintf(ofp, "#ifdef VISION_TABLES\n");
#ifdef VISION_TABLES
    H_close_gen();
    H_far_gen();
#endif /* VISION_TABLES */
    Fprintf(ofp, "\n#endif /* VISION_TABLES */\n");
    Fclose(ofp);

    SpinCursor(3);

    /*
     * create the source file, "vis_tab.c"
     */
    filename[0] = '\0';
#ifdef FILE_PREFIX
    Strcat(filename, file_prefix);
#endif
    Sprintf(eos(filename), SOURCE_TEMPLATE, VIS_TAB_C);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        /* creating vis_tab.c failed; remove the vis_tab.h we just made */
        filename[0] = '\0';
#ifdef FILE_PREFIX
        Strcat(filename, file_prefix);
#endif
        Sprintf(eos(filename), INCLUDE_TEMPLATE, VIS_TAB_H);
        Unlink(filename);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp, "%s", Dont_Edit_Code);
    Fprintf(ofp, "#include \"config.h\"\n");
    Fprintf(ofp, "#ifdef VISION_TABLES\n");
    Fprintf(ofp, "#include \"vis_tab.h\"\n");

    SpinCursor(3);

#ifdef VISION_TABLES
    C_close_gen();
    C_far_gen();
    Fprintf(ofp, "\nvoid vis_tab_init() { return; }\n");
#endif /* VISION_TABLES */

    SpinCursor(3);

    Fprintf(ofp, "\n#endif /* VISION_TABLES */\n");
    Fprintf(ofp, "\n/*vis_tab.c*/\n");

    Fclose(ofp);
    return;
}

#ifdef VISION_TABLES

/*--------------  vision tables  --------------*\
 *
 *  Generate the close and far tables.  This is done by setting up a
 *  fake dungeon and moving our source to different positions relative
 *  to a block and finding the first/last visible position.  The fake
 *  dungeon is all clear execpt for the upper left corner (BLOCK_HEIGHT
 *  by BLOCK_WIDTH) is blocked.  Then we move the source around relative
 *  to the corner of the block.  For each new position of the source
 *  we check positions on rows "kittycorner" from the source.  We check
 *  positions until they are either in sight or out of sight (depends on
 *  which table we are generating).  The picture below shows the setup
 *  for the generation of the close table.  The generation of the far
 *  table would switch the quadrants of the '@' and the "Check rows
 *  here".
 *
 *
 *  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
 *  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
 *  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,,,,,,,,, Check rows here ,,,,,,,,,,,,
 *  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
 *  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXB,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
 *  ...............................
 *  ...............................
 *  .........@.....................
 *  ...............................
 *
 *      Table generation figure (close_table).  The 'X's are blocked points.
 *      The 'B' is a special blocked point.  The '@' is the source.  The ','s
 *      are the target area.  The '.' are just open areas.
 *
 *
 *  Example usage of close_table[][][].
 *
 *  The table is as follows:
 *
 *      dy = |row of '@' - row of 'B'|  - 1
 *      dx = |col of '@' - col of 'B'|
 *
 *  The first indices are the deltas from the source '@' and the block 'B'.
 *  You must check for the value inside the abs value bars being zero.  If
 *  so then the block is on the same row and you don't need to do a table
 *  lookup.  The last value:
 *
 *      dcy = |row of block - row to be checked|
 *
 *  Is the value of the first visible spot on the check row from the
 *  block column.  So
 *
 *  first visible col = close_table[dy][dx][dcy] + col of 'B'
 *
\*--------------  vision tables  --------------*/

static void
H_close_gen()
{
    Fprintf(ofp, "\n/* Close */\n");
    Fprintf(ofp,
            "#define CLOSE_MAX_SB_DY %2d\t/* |src row - block row| - 1\t*/\n",
            TEST_HEIGHT - 1);
    Fprintf(ofp,
            "#define CLOSE_MAX_SB_DX %2d\t/* |src col - block col|\t*/\n",
            TEST_WIDTH);
    Fprintf(ofp,
            "#define CLOSE_MAX_BC_DY %2d\t/* |block row - check row|\t*/\n",
            TEST_HEIGHT);
    Fprintf(ofp, "typedef struct {\n");
    Fprintf(ofp,
            "    unsigned char close[CLOSE_MAX_SB_DX][CLOSE_MAX_BC_DY];\n");
    Fprintf(ofp, "} close2d;\n");
    Fprintf(ofp, "extern close2d close_table[CLOSE_MAX_SB_DY];\n");
    return;
}

static void
H_far_gen()
{
    Fprintf(ofp, "\n/* Far */\n");
    Fprintf(ofp, "#define FAR_MAX_SB_DY %2d\t/* |src row - block row|\t*/\n",
            TEST_HEIGHT);
    Fprintf(ofp,
            "#define FAR_MAX_SB_DX %2d\t/* |src col - block col| - 1\t*/\n",
            TEST_WIDTH - 1);
    Fprintf(ofp,
            "#define FAR_MAX_BC_DY %2d\t/* |block row - check row| - 1\t*/\n",
            TEST_HEIGHT - 1);
    Fprintf(ofp, "typedef struct {\n");
    Fprintf(ofp, "    unsigned char far_q[FAR_MAX_SB_DX][FAR_MAX_BC_DY];\n");
    Fprintf(ofp, "} far2d;\n");
    Fprintf(ofp, "extern far2d far_table[FAR_MAX_SB_DY];\n");
    return;
}

static void
C_close_gen()
{
    int i, dx, dy;
    int src_row, src_col;     /* source */
    int block_row, block_col; /* block */
    int this_row;
    int no_more;
    const char *delim;

    block_row = BLOCK_HEIGHT - 1;
    block_col = BLOCK_WIDTH - 1;

    Fprintf(ofp, "\n#ifndef FAR_TABLE_ONLY\n");
    Fprintf(ofp, "\nclose2d close_table[CLOSE_MAX_SB_DY] = {\n");
#ifndef no_vision_progress
    Fprintf(stderr, "\nclose:");
#endif

    for (dy = 1; dy < TEST_HEIGHT; dy++) {
        src_row = block_row + dy;
        Fprintf(ofp, "/* DY = %2d (- 1)*/\n  {{\n", dy);
#ifndef no_vision_progress
        Fprintf(stderr, " %2d", dy), (void) fflush(stderr);
#endif
        for (dx = 0; dx < TEST_WIDTH; dx++) {
            src_col = block_col - dx;
            Fprintf(ofp, "  /*%2d*/ {", dx);

            no_more = 0;
            for (this_row = 0; this_row < TEST_HEIGHT; this_row++) {
                delim = (this_row < TEST_HEIGHT - 1) ? "," : "";
                if (no_more) {
                    Fprintf(ofp, "%s%s", CLOSE_OFF_TABLE_STRING, delim);
                    continue;
                }
                SpinCursor(3);

                /* Find the first column that we can see. */
                for (i = block_col + 1; i < MAX_COL; i++) {
                    if (clear_path(src_row, src_col, block_row - this_row, i))
                        break;
                }

                if (i == MAX_COL)
                    no_more = 1;
                Fprintf(ofp, "%2d%s", i - block_col, delim);
            }
            Fprintf(ofp, "}%s", (dx < TEST_WIDTH - 1) ? ",\n" : "\n");
        }
        Fprintf(ofp, "  }},\n");
    }

    Fprintf(ofp, "}; /* close_table[] */\n"); /* closing brace for table */
    Fprintf(ofp, "#endif /* !FAR_TABLE_ONLY */\n");
#ifndef no_vision_progress
    Fprintf(stderr, "\n");
#endif
    return;
}

static void
C_far_gen()
{
    int i, dx, dy;
    int src_row, src_col;     /* source */
    int block_row, block_col; /* block */
    int this_row;
    const char *delim;

    block_row = BLOCK_HEIGHT - 1;
    block_col = BLOCK_WIDTH - 1;

    Fprintf(ofp, "\n#ifndef CLOSE_TABLE_ONLY\n");
    Fprintf(ofp, "\nfar2d far_table[FAR_MAX_SB_DY] = {\n");
#ifndef no_vision_progress
    Fprintf(stderr, "\n_far_:");
#endif

    for (dy = 0; dy < TEST_HEIGHT; dy++) {
        src_row = block_row - dy;
        Fprintf(ofp, "/* DY = %2d */\n  {{\n", dy);
#ifndef no_vision_progress
        Fprintf(stderr, " %2d", dy), (void) fflush(stderr);
#endif
        for (dx = 1; dx < TEST_WIDTH; dx++) {
            src_col = block_col + dx;
            Fprintf(ofp, "  /*%2d(-1)*/ {", dx);

            for (this_row = block_row + 1; this_row < block_row + TEST_HEIGHT;
                 this_row++) {
                delim = (this_row < block_row + TEST_HEIGHT - 1) ? "," : "";

                SpinCursor(3);
                /* Find first col that we can see. */
                for (i = 0; i <= block_col; i++) {
                    if (clear_path(src_row, src_col, this_row, i))
                        break;
                }

                if (block_col - i < 0)
                    Fprintf(ofp, "%s%s", FAR_OFF_TABLE_STRING, delim);
                else
                    Fprintf(ofp, "%2d%s", block_col - i, delim);
            }
            Fprintf(ofp, "}%s", (dx < TEST_WIDTH - 1) ? ",\n" : "\n");
        }
        Fprintf(ofp, "  }},\n");
    }

    Fprintf(ofp, "}; /* far_table[] */\n"); /* closing brace for table */
    Fprintf(ofp, "#endif /* !CLOSE_TABLE_ONLY */\n");
#ifndef no_vision_progress
    Fprintf(stderr, "\n");
#endif
    return;
}

/*
 *  "Draw" a line from the hero to the given location.  Stop if we hit a
 *  wall.
 *
 *  Generalized integer Bresenham's algorithm (fast line drawing) for
 *  all quadrants.  From _Procedural Elements for Computer Graphics_, by
 *  David F. Rogers.  McGraw-Hill, 1985.
 *
 *  I have tried a little bit of optimization by pulling compares out of
 *  the inner loops.
 *
 *  NOTE:  This had better *not* be called from a position on the
 *  same row as the hero.
 */
static int
clear_path(you_row, you_col, y2, x2)
int you_row, you_col, y2, x2;
{
    int dx, dy, s1, s2;
    register int i, error, x, y, dxs, dys;

    x = you_col;
    y = you_row;
    dx = abs(x2 - you_col);
    dy = abs(y2 - you_row);
    s1 = sign(x2 - you_col);
    s2 = sign(y2 - you_row);

    if (s1 == 0) {     /* same column */
        if (s2 == 1) { /* below (larger y2 value) */
            for (i = you_row + 1; i < y2; i++)
                if (!xclear[i][you_col])
                    return 0;
        } else { /* above (smaller y2 value) */
            for (i = y2 + 1; i < you_row; i++)
                if (!xclear[i][you_col])
                    return 0;
        }
        return 1;
    }

    /*
     *  Lines at 0 and 90 degrees have been weeded out.
     */
    if (dy > dx) {
        error = dx;
        dx = dy;
        dy = error;    /* swap the values */
        dxs = dx << 1; /* save the shifted values */
        dys = dy << 1;
        error = dys - dx; /* NOTE: error is used as a temporary above */

        for (i = 0; i < dx; i++) {
            if (!xclear[y][x])
                return 0; /* plot point */

            while (error >= 0) {
                x += s1;
                error -= dxs;
            }
            y += s2;
            error += dys;
        }
    } else {
        dxs = dx << 1; /* save the shifted values */
        dys = dy << 1;
        error = dys - dx;

        for (i = 0; i < dx; i++) {
            if (!xclear[y][x])
                return 0; /* plot point */

            while (error >= 0) {
                y += s2;
                error -= dxs;
            }
            x += s1;
            error += dys;
        }
    }
    return 1;
}
#endif /* VISION_TABLES */

#ifdef STRICT_REF_DEF
NEARDATA struct flag flags;
#ifdef ATTRIB_H
struct attribs attrmax, attrmin;
#endif
#endif /* STRICT_REF_DEF */

/*makedefs.c*/
